#include <commom_ipc.h>

#ifndef WIN32
namespace mdk
{
    MallocMem::MallocMem(int byte)
    {
        pBuffer = new char[byte];
        length = byte;
    }

    char *MallocMem::c_str()
    {
        return pBuffer;
    }

    int MallocMem::set_buffer(char *pdata)
    {
        memset(pBuffer, 0, length);
        memcpy(pBuffer, pdata, length);

        return SUCCESS;
    }

    int MallocMem::get_buffer(char *pdata)
    {
        memcpy(pdata, pBuffer, length);
        memset(pBuffer, 0, length);

        return SUCCESS;
    }

    int MallocMem::size()
    {
        return length;
    }

    MallocMem::~MallocMem()
    {
        delete[] pBuffer;
    }

    MessageData::MessageData()
    {
        m_type = 0;
        valid_size = 0;
        cmd = 0;
        memset(m_buffer, 0, sizeof(m_buffer));
    }

    int MessageData::ImportBuffer(char *pdata)
    {
        memset(m_buffer, 0, sizeof(m_buffer));
        memcpy(m_buffer, pdata, sizeof(m_buffer));

        return SUCCESS;
    }

    int MessageData::ExportBuffer(char *pdata)
    {
        memcpy(pdata, m_buffer, sizeof(m_buffer));
        memset(m_buffer, 0, sizeof(m_buffer));

        return SUCCESS;
    }

    Message::Message(key_t key)
    {
        msg_id = msgget(key, 0666 | IPC_CREAT);
        if (msg_id == -1)
        {
            LOG_ERROR("create_msg failed!\n");
        }
    }

    int Message::send_msg()
    {
        if (msgsnd(msg_id, &msg, sizeof(msg) - sizeof(long int), 0) == -1)
        {
            LOG_ERROR("send_msg failed!\n");
        }
        memset((void *)&msg, 0, sizeof(msg));

        return SUCCESS;
    }

    int Message::recv_msg(long int type)
    {
        memset((void *)&msg, 0, sizeof(msg));
        if (msgrcv(msg_id, &msg, sizeof(msg) - sizeof(long int), type, 0) == -1)
        {
            LOG_ERROR("msg_rev failed!\n");
        }
        return SUCCESS;
    }

    int Message::set_message(MessageData *message)
    {
        memset((void *)&msg, 0, sizeof(msg));
        memcpy(&msg, message, sizeof(msg));

        return SUCCESS;
    }

    int Message::get_message(MessageData *message)
    {
        memcpy(message, &msg, sizeof(msg));
        memset((void *)&msg, 0, sizeof(msg));

        return SUCCESS;
    }

    int Message::delete_msg()
    {
        if (msgctl(msg_id, IPC_RMID, NULL) == -1)
        {
            LOG_ERROR("msg_del failed!\n");
        }

        return SUCCESS;
    }

    ShareMemoryData::ShareMemoryData()
    {
        valid_size = 0;
        memset(buffer, 0, sizeof(buffer));
    }

    ShareMemory::ShareMemory(key_t key)
    {
        shm_id = shmget(key, sizeof(ShareMemoryData), 0666 | IPC_CREAT);
        if (shm_id == -1)
        {
            LOG_ERROR("shmget failed!\n");
        }

        shm = (ShareMemoryData *)shmat(shm_id, NULL, 0);
        if (shm == NULL)
        {
            LOG_ERROR("shmat failed!\n");
        }
    }

    int ShareMemory::get_shm(ShareMemoryData *shmptr)
    {
        memcpy(shmptr, shm, sizeof(ShareMemoryData));
        memset((void *)shm, 0, sizeof(ShareMemoryData));

        return SUCCESS;
    }

    int ShareMemory::set_shm(ShareMemoryData *shmptr)
    {
        memset((void *)shm, 0, sizeof(ShareMemoryData));
        memcpy(shm, shmptr, sizeof(ShareMemoryData));

        return SUCCESS;
    }

    int ShareMemory::delete_shm()
    {
        shmdt(shm);
        shmctl(shm_id, IPC_RMID, NULL);
        return SUCCESS;
    }

    /*
    * @brief:Create Semaphore
    * @param key: key value
    * @param num_sems: Semaphore numbers you want to create
    * @retval: No
    */

    Semaphore::Semaphore(key_t key, int num_sems)
    {
        sem_set_id = semget(key, num_sems, 0666 | IPC_CREAT);
        if (sem_set_id == -1)
        {
            LOG_ERROR("semget failed!\n");
        }
    }

    /*
    * @brief:Init Semaphore
    * @param index: the index of Semaphore you create
    * @param value: Semaphore init value
    * @retval: 0
    */

    int Semaphore::InitSem(int index, int value)
    {
        sem.val = value;
        if (semctl(sem_set_id, index, SETVAL, sem) == -1)
        {
            LOG_ERROR("set_sem failed!\n");
        }
        return SUCCESS;
    }

    int Semaphore::WaitSem(int index)
    {
        struct sembuf sem_buf;
        sem_buf.sem_num = index;
        sem_buf.sem_op = -1;
        sem_buf.sem_flg = SEM_UNDO;
        if (semop(sem_set_id, &sem_buf, 1) == -1)
        {
            LOG_ERROR("WaitSem failed");
        }

        return SUCCESS;
    }

    int Semaphore::SignalSem(int index)
    {
        struct sembuf sem_buf;
        sem_buf.sem_num = index;
        sem_buf.sem_op = 1;
        sem_buf.sem_flg = SEM_UNDO;
        if (semop(sem_set_id, &sem_buf, 1) == -1)
        {
            LOG_ERROR("SignalSem failed");
        }

        return SUCCESS;
    }

    int Semaphore::DeleteSem(int index)
    {
        if (semctl(sem_set_id, index, IPC_RMID, sem) == -1)
        {
            LOG_ERROR("deletet sem failed!\n");
        }

        return SUCCESS;
    }

    SocketDataHead::SocketDataHead()
    {
        cmd = 0;
        valid_size = 0;
    }

    SocketData::SocketData()
    {
        memset(buffer, 0, sizeof(buffer));
    }

    int SocketData::set_cmd(int cmd)
    {
        data_head.cmd = cmd;
        return SUCCESS;
    }

    int SocketData::get_cmd()
    {
        return data_head.cmd;
    }

    int SocketData::get_valid_size()
    {
        return data_head.valid_size;
    }

    int SocketData::set_valid_size(int valid_size)
    {
        data_head.valid_size = valid_size;
        return SUCCESS;
    }

    int SocketData::ImportBuffer(char *pdata)
    {
        memset(buffer, 0, sizeof(buffer));
        memcpy(buffer, pdata, sizeof(buffer));

        return SUCCESS;
    }

    int SocketData::ExportBuffer(char *pdata)
    {
        memcpy(pdata, buffer, sizeof(buffer));
        memset(buffer, 0, sizeof(buffer));

        return SUCCESS;
    }

    int SocketSession::CommomSessionInit(int domain, int type, int backlog)
    {
        sock_type = type;
        sock_domain = domain;
        new_fd = 0;
        sock_backlog = backlog;
        sock_port = 0;
        sock_path = NULL;
        sock_ip = NULL;
        sock_path_size = 0;
        memset(&RemoteAddr, 0, sizeof(RemoteAddr));
        memset(&LocalAddr, 0, sizeof(LocalAddr));
        memset(&client_addr, 0, sizeof(client_addr));
        memset(&server_addr, 0, sizeof(server_addr));

        listen_fd = socket(domain, type, 0);
        if (listen_fd == -1)
        {
            LOG_ERROR("create_socket failed!\n");
        }

        if (domain != AF_UNIX)
        {
            int opt = 1;
            if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
            {
                LOG_ERROR("setsockopt failed!\n");
            }
        }
        return SUCCESS;
    }
    /*
    * @brief:Create AF_INET socket session for TCP or UDP
    * @param ipaddr: if the session is server, ipaddr should be set NULL
                              if the session is client, ipaddr should be set specific address you want to connect
    * @param port: the specific port you want to bind
    * @param domain: the protocol, example AF_INET
    * @param type: the protocol type, example SOCK_STREAM or SOCK_DGRAM
    * @param backlog: Maximum length of queue waiting for connection
                   Note:     if the session is TCP server, the value should be
                                set correctly. Other conditions, the value should be set 0
    * @retval: No
    */

    SocketSession::SocketSession(char *ipaddr, int port, int domain, int type, int backlog)
    {
        CommomSessionInit(domain, type, backlog);

        sock_port = port;
        if (ipaddr != NULL)
        {
            sock_ip = new char[16];
            memcpy(sock_ip, ipaddr, 16);
        }
    }

    /*
    * @brief:Create AF_UNIX socket session for TCP or UDP

    * @param domain: the protocol, example AF_UNIX
    * @param type: the protocol type, example SOCK_STREAM or SOCK_DGRAM
    * @param backlog: Maximum length of queue waiting for connection
               Must Note: if the session is TCP server, the value should be
                                set correctly. if the session is UDP server, the
                                value should be set 1. Other conditions, the value should be set 0
    * @param path: if the session is server, path you want to create should be set correctly
                           if the session is client, path should be set specific address you want to connect
    * @param path_size: the path size
    * @retval: No
    */

    SocketSession::SocketSession(int domain, int type, int backlog, char *path, int path_size)
    {
        CommomSessionInit(domain, type, backlog);

        sock_path = new char[path_size];
        memcpy(sock_path, path, path_size);
        sock_path_size = path_size;
    }

    SocketSession::~SocketSession()
    {
        if (sock_path != NULL)
        {
            delete[] sock_path;
        }
        if (sock_ip != NULL)
        {
            delete[] sock_ip;
        }
    }

    int SocketSession::SetSocketData(SocketData *pdata)
    {
        memset((void *)&sockdata, 0, sizeof(SocketData));
        memcpy(&sockdata, pdata, sizeof(SocketData));

        return SUCCESS;
    }

    int SocketSession::GetSocketData(SocketData *pdata)
    {
        memcpy(pdata, &sockdata, sizeof(SocketData));
        memset((void *)&sockdata, 0, sizeof(SocketData));

        return SUCCESS;
    }

    int SocketSession::InitServerSocket()
    {
        switch (sock_domain)
        {
        case AF_INET:
            LocalAddr.sin_family = AF_INET;
            LocalAddr.sin_port = htons(sock_port);
            LocalAddr.sin_addr.s_addr = htonl(INADDR_ANY);
            if (bind(listen_fd, (struct sockaddr *)&LocalAddr, sizeof(struct sockaddr)) == -1)
            {
                LOG_ERROR("sock_bind failed!\n");
            }
            if (sock_type == SOCK_STREAM)
            {
                if (listen(listen_fd, sock_backlog) == -1)
                {
                    LOG_ERROR("sock_listen failed!\n");
                }
            }
            break;
        case AF_UNIX:
            if (sock_backlog != UNIX_OVER_UDP_CLIENT) //tcp or udp:  sock_backlog not equal to 0
            {
                unlink(sock_path);
                server_addr.sun_family = AF_UNIX;
                strcpy(server_addr.sun_path, sock_path);
                bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
                if (sock_type == SOCK_STREAM)
                {
                    listen(listen_fd, sock_backlog);
                }
            }
            else if (sock_backlog == UNIX_OVER_UDP_CLIENT)
            {
                client_addr.sun_family = AF_UNIX;
                char str[] = "_client";
                int length = sizeof(str);
                char sock_path_client[length + sock_path_size];
                sprintf(sock_path_client, "%s%s", sock_path, str);
                unlink(sock_path_client);
                strcpy(client_addr.sun_path, sock_path_client);
                bind(listen_fd, (struct sockaddr *)&client_addr, sizeof(client_addr));
            }
            break;
        default:
            break;
        }
        return SUCCESS;
    }

    int SocketSession::SetNoBlock()
    {
        int flag = fcntl(listen_fd, F_GETFL, 0);
        flag |= O_NONBLOCK;
        if (fcntl(listen_fd, F_SETFL, flag) < 0)
        {
            LOG_ERROR("fcntl failed.\n");
        }

        return SUCCESS;
    }

    int SocketSession::SocketAccept()
    {
        socklen_t len = 0;
        switch (sock_domain)
        {
        case AF_INET:
            len = sizeof(struct sockaddr);
            new_fd = accept(listen_fd, (struct sockaddr *)&RemoteAddr, &len);
            if (new_fd == -1)
            {
                LOG_ERROR("sock_accept failed!\n");
            }
            LOG_INFO("new_fd: %d\n", new_fd);
            LOG_INFO("connected from %s, port: %d\n", inet_ntoa(RemoteAddr.sin_addr), ntohs(RemoteAddr.sin_port));
            break;
        case AF_UNIX:
            len = sizeof(client_addr);
            new_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len);
            LOG_INFO("connection from %s\n", client_addr.sun_path);
            break;
        default:
            break;
        }

        return SUCCESS;
    }

    int SocketSession::SocketConnect()
    {
        struct sockaddr_in remote_addr;
        int ret = 0;
        switch (sock_domain)
        {
        case AF_INET:
            memset((char *)&remote_addr, 0, sizeof(struct sockaddr_in));
            remote_addr.sin_family = AF_INET;
            remote_addr.sin_port = htons(sock_port);
            remote_addr.sin_addr.s_addr = inet_addr(sock_ip);
            if (connect(listen_fd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
            {
                LOG_ERROR("sock_connect failed!\n");
            }
            break;
        case AF_UNIX:
            client_addr.sun_family = AF_UNIX;
            strcpy(client_addr.sun_path, sock_path);
            ret = connect(listen_fd, (struct sockaddr *)&client_addr, sizeof(client_addr));
            if (ret == -1)
            {
                LOG_ERROR("connect failed: ");
            }
            break;
        default:
            break;
        }

        return SUCCESS;
    }

    int SocketSession::CommomOverTCPSend(int sockfd)
    {
        int bytes;
        bytes = send(sockfd, &sockdata.data_head, sizeof(sockdata.data_head), 0);
        if (bytes == -1)
        {
            LOG_ERROR("send data head failed!\n");
        }

        int nbytes = 0;
        char *Addr = sockdata.buffer;
        int sent_size = 0;
        int each_send_size = MAX_SOCKET_EACH_SEND_SIZE;
        int remain_send_size = sockdata.data_head.valid_size;
        while (sent_size != sockdata.data_head.valid_size)
        {
            if (remain_send_size < MAX_SOCKET_EACH_SEND_SIZE)
            {
                each_send_size = remain_send_size;
            }
            nbytes = send(sockfd, Addr, each_send_size, 0);
            if (nbytes == -1)
            {
                LOG_ERROR("sock send data failed!\n");
                break;
            }
            sent_size += nbytes;
            Addr += nbytes;
            remain_send_size -= nbytes;
        }
        memset((void *)&sockdata, 0, sizeof(SocketData));

        return bytes + sent_size;
    }

    int SocketSession::CommomOverTCPRecv(int sockfd)
    {
        memset((void *)&sockdata, 0, sizeof(SocketData));
        int bytes = 0;
        bytes = recv(sockfd, &sockdata.data_head, sizeof(sockdata.data_head), 0);
        if (bytes == -1)
        {
            LOG_ERROR("recv data_head failed!\n");
        }

        int nbytes = 0;
        char *Addr = sockdata.buffer;
        int received_size = 0;
        int each_recv_size = MAX_SOCKET_EACH_SEND_SIZE;
        int remain_recv_size = sockdata.data_head.valid_size;

        while (received_size != sockdata.data_head.valid_size)
        {
            if (remain_recv_size < MAX_SOCKET_EACH_SEND_SIZE)
            {
                each_recv_size = remain_recv_size;
            }
            nbytes = recv(sockfd, Addr, each_recv_size, 0);
            if (nbytes == -1)
            {
                LOG_ERROR("sock recv data failed!\n");
                break;
            }
            received_size += nbytes;
            Addr += nbytes;
            remain_recv_size -= nbytes;
        }

        return bytes + received_size;
    }

    int SocketSession::SocketSend()
    {
        int bytes = 0;
        switch (sock_domain)
        {
        case AF_INET:
            switch (sock_type)
            {
            case SOCK_STREAM:
                if (new_fd != 0)
                {
                    bytes = CommomOverTCPSend(new_fd);
                }
                else if (new_fd == 0)
                {
                    bytes = CommomOverTCPSend(listen_fd);
                }
                break;
            case SOCK_DGRAM:
                if (sock_ip == NULL)
                {
                    bytes = CommomOverUDPSend(&RemoteAddr, NULL, 0);
                }
                else if (sock_ip != NULL)
                {
                    bytes = CommomOverUDPSend(&RemoteAddr, sock_ip, sock_port);
                }
                break;
            default:
                break;
            }
            break;
        case AF_UNIX:
            switch (sock_type)
            {
            case SOCK_STREAM:
                if (new_fd != 0)
                {
                    bytes = CommomOverTCPSend(new_fd);
                }
                else if (new_fd == 0)
                {
                    bytes = CommomOverTCPSend(listen_fd);
                }
                break;
            case SOCK_DGRAM:
                if (sock_backlog == UNIX_OVER_UDP_SERVER)
                {
                    bytes = CommomOverUDPSend((struct sockaddr_in *)&client_addr, NULL, 0);
                }
                else if (sock_backlog == UNIX_OVER_UDP_CLIENT)
                {
                    bytes = CommomOverUDPSend((struct sockaddr_in *)&server_addr, NULL, 0);
                }
                break;
            default:
                break;
            }
            break;
        default:
            break;
        }

        return bytes;
    }

    int SocketSession::SocketRecv()
    {
        int bytes = 0;
        switch (sock_domain)
        {
        case AF_INET:
            switch (sock_type)
            {
            case SOCK_STREAM:
                if (new_fd != 0)
                {
                    bytes = CommomOverTCPRecv(new_fd);
                }
                else if (new_fd == 0)
                {
                    bytes = CommomOverTCPRecv(listen_fd);
                }
                break;
            case SOCK_DGRAM:
                if (sock_ip == NULL)
                {
                    bytes = CommomOverUDPRecv(&RemoteAddr);
                    LOG_INFO("connected from %s, port: %d\n", inet_ntoa(RemoteAddr.sin_addr), ntohs(RemoteAddr.sin_port));
                }
                else if (sock_ip != NULL)
                {
                    bytes = CommomOverUDPRecv(&LocalAddr);
                }
                break;
            default:
                break;
            }
            break;
        case AF_UNIX:
            switch (sock_type)
            {
            case SOCK_STREAM:
                if (new_fd != 0)
                {
                    bytes = CommomOverTCPRecv(new_fd);
                }
                else if (new_fd == 0)
                {
                    bytes = CommomOverTCPRecv(listen_fd);
                }
                break;
            case SOCK_DGRAM:
                if (sock_backlog == UNIX_OVER_UDP_SERVER)
                {
                    bytes = CommomOverUDPRecv((struct sockaddr_in *)&client_addr);
                    LOG_INFO("connection from %s\n", client_addr.sun_path);
                }
                else if (sock_backlog == UNIX_OVER_UDP_CLIENT)
                {
                    bytes = CommomOverUDPRecv((struct sockaddr_in *)&client_addr);
                }
                break;
            default:
                break;
            }
            break;
        default:
            break;
        }

        return bytes;
    }

    int SocketSession::CommomOverUDPSend(struct sockaddr_in *sock_addr, char *ipaddr, int port)
    {
        if (ipaddr != NULL && port != 0)
        {
            sock_addr->sin_family = AF_INET;
            sock_addr->sin_port = htons(port);

            if (inet_pton(AF_INET, ipaddr, &sock_addr->sin_addr) < 0)
            {
                LOG_ERROR("inet_pton failed.\n");
            }
        }
        socklen_t len = 0;
        if (sock_domain == AF_INET)
        {
            len = sizeof(struct sockaddr_in);
        }
        else if (sock_domain == AF_UNIX)
        {
            len = sizeof(struct sockaddr_un);
            if (sock_backlog == UNIX_OVER_UDP_CLIENT)
            {
                struct sockaddr_un *temp = (struct sockaddr_un *)sock_addr;
                temp->sun_family = AF_UNIX;
                strcpy(temp->sun_path, sock_path);
            }
        }
        int bytes;
        bytes = sendto(listen_fd, &sockdata.data_head, sizeof(sockdata.data_head), 0, (struct sockaddr *)sock_addr, len);
        if (bytes == -1)
        {
            LOG_ERROR("send data head failed!\n");
        }

        int nbytes = 0;
        char *Addr = sockdata.buffer;
        int sent_size = 0;
        int each_send_size = MAX_SOCKET_EACH_SEND_SIZE;
        int remain_send_size = sockdata.data_head.valid_size;
        while (sent_size != sockdata.data_head.valid_size)
        {
            if (remain_send_size < MAX_SOCKET_EACH_SEND_SIZE)
            {
                each_send_size = remain_send_size;
            }
            nbytes = sendto(listen_fd, Addr, each_send_size, 0, (struct sockaddr *)sock_addr, len);
            if (nbytes == -1)
            {
                LOG_ERROR("sock send data failed!\n");
                break;
            }
            sent_size += nbytes;
            Addr += nbytes;
            remain_send_size -= nbytes;
        }
        memset((void *)&sockdata, 0, sizeof(SocketData));

        return bytes + sent_size;
    }

    int SocketSession::CommomOverUDPRecv(struct sockaddr_in *sock_addr)
    {
        memset((void *)&sockdata, 0, sizeof(SocketData));
        int bytes = 0;
        socklen_t len = 0;
        if (sock_domain == AF_INET)
        {
            len = sizeof(struct sockaddr_in);
        }
        else if (sock_domain == AF_UNIX)
        {
            len = sizeof(struct sockaddr_un);
        }

        bytes = recvfrom(listen_fd, &sockdata.data_head, sizeof(sockdata.data_head), 0, (struct sockaddr *)sock_addr, &len);
        if (bytes == -1)
        {
            LOG_ERROR("recv data_head failed!\n");
        }

        int nbytes = 0;
        char *Addr = sockdata.buffer;
        int received_size = 0;
        int each_recv_size = MAX_SOCKET_EACH_SEND_SIZE;
        int remain_recv_size = sockdata.data_head.valid_size;

        while (received_size != sockdata.data_head.valid_size)
        {
            if (remain_recv_size < MAX_SOCKET_EACH_SEND_SIZE)
            {
                each_recv_size = remain_recv_size;
            }
            nbytes = recvfrom(listen_fd, Addr, each_recv_size, 0, (struct sockaddr *)sock_addr, &len);
            if (nbytes == -1)
            {
                LOG_ERROR("sock recv data failed!\n");
                break;
            }
            received_size += nbytes;
            Addr += nbytes;
            remain_recv_size -= nbytes;
        }

        return bytes + received_size;
    }

    int SocketSession::SetSocketSendBufSize(int size)
    {
        if (setsockopt(listen_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1)
        {
            LOG_ERROR("setsockopt sendbufsize fail!\n");
        }

        return SUCCESS;
    }

    int SocketSession::SetSocketRecvBufSize(int size)
    {
        if (setsockopt(listen_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) == -1)
        {
            LOG_ERROR("setsockopt sendbufsize fail!\n");
        }

        return SUCCESS;
    }

    int SocketSession::SocketCloseClient()
    {
        close(new_fd);

        return SUCCESS;
    }

    int SocketSession::SocketCloseListen()
    {
        close(listen_fd);

        return SUCCESS;
    }
}
#endif